// MvxSwissBindingParser.cs
// (c) Copyright Cirrious Ltd. http://www.cirrious.com
// MvvmCross is licensed using Microsoft Public License (Ms-PL)
// Contributions and inspirations noted in readme.md and license.txt
//
// Project Lead - Stuart Lodge, @slodge, me@slodge.com

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using T4UtilityBelt.UxDesigns.Bindings.Parser.Interfaces.Parse;
using T4UtilityBelt.UxDesigns.Bindings.Parser.Other;

namespace T4UtilityBelt.UxDesigns.Bindings.Parser.Parse
{
    public class MvxSwissBindingParser
        : MvxBaseParser
          , IMvxBindingParser
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public bool TryParseBindingDescription(string text, out Binding requestedDescription)
        {
            try
            {
                Reset(text);
                requestedDescription = ParseBindingDescription();
                return true;
            }
            catch (Exception exception)
            {
                MvxBindingTrace.Trace(MvxTraceLevel.Error,
                                      "Problem parsing Swiss binding {0}", exception.Message);
                requestedDescription = null;
                return false;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public bool TryParseBindingSpecification(string text, out MvxSerializableBindingSpecification requestedBindings)
        {
            try
            {
                Reset(text);

                var toReturn = new MvxSerializableBindingSpecification();
                while (!IsComplete)
                {
                    SkipWhiteSpaceAndDescriptionSeparators();
                    KeyValuePair<string, Binding> result = ParseTargetPropertyNameAndDescription();
                    toReturn[result.Key] = result.Value;
                    SkipWhiteSpaceAndDescriptionSeparators();
                }

                requestedBindings = toReturn;
                return true;
            }
            catch (Exception exception)
            {
                MvxBindingTrace.Trace(MvxTraceLevel.Error,
                                      "Problem parsing Swiss binding {0}", exception.Message);
                requestedBindings = null;
                return false;
            }
        }

        private KeyValuePair<string, Binding> ParseTargetPropertyNameAndDescription()
        {
            string targetPropertyName = ReadTargetPropertyName();
            SkipWhiteSpace();
            Binding description = ParseBindingDescription();
            return new KeyValuePair<string, Binding>(targetPropertyName, description);
        }

        private void ParseNextBindingDescriptionOptionInto(Binding description)
        {
            if (IsComplete)
                return;

            string block = ReadTextUntilNonQuotedOccurrenceOfAnyOf('=', ',', ';');
            block = block.Trim();
            if (string.IsNullOrEmpty(block))
            {
                return;
            }

            switch (block)
            {
                case "Path":
                    ParseEquals(block);
                    description.SourcePropertyPath = ReadTextUntilNonQuotedOccurrenceOfAnyOf(',', ';');
                    break;

                case "Converter":
                    ParseEquals(block);
                    description.Converter = ReadTextUntilNonQuotedOccurrenceOfAnyOf(',', ';');
                    break;

                case "ConverterParameter":
                    ParseEquals(block);
                    description.ConverterParameter = ReadValue();
                    break;

                case "FallbackValue":
                    ParseEquals(block);
                    description.FallbackValue = ReadValue();
                    break;

                case "Mode":
                    ParseEquals(block);

                    //if (description.Mode != MvxBindingMode.Default)
                    //{
                    //    MvxBindingTrace.Trace(MvxTraceLevel.Warning, "Mode specified multiple times in binding in {0} - for readability either use <,>,<1,<> or use (Mode=...) - not both", FullText);
                    //}
                    description.Mode = ReadBindingMode();
                    break;

                default:
                    if (!string.IsNullOrEmpty(description.SourcePropertyPath))
                    {
                        throw new BindingException(
                            string.Format(
                                "You cannot specify Path more than once - first Path '{0}', second Path '{1}', position {2} in {3}",
                                description.SourcePropertyPath, block, CurrentIndex, FullText));
                    }
                    description.SourcePropertyPath = block;
                    break;
            }
        }

        private void ParseEquals(string block)
        {
            if (IsComplete)
                throw new BindingException(string.Format("Cannot terminate binding expression during option {0} in {1}",
                                                  block,
                                                  FullText));
            if (CurrentChar != '=')
                throw new BindingException(string.Format("Must follow binding option {0} with an '=' in {1}",
                                                  block,
                                                  FullText));

            MoveNext();
            if (IsComplete)
                throw new BindingException(string.Format("Cannot terminate binding expression during option {0} in {1}",
                                                  block,
                                                  FullText));
        }

        private Binding ParseBindingDescription()
        {
            var description = new Binding();
            SkipWhiteSpace();

            while (true)
            {
                ParseNextBindingDescriptionOptionInto(description);

                SkipWhiteSpace();
                if (IsComplete)
                    return description;

                switch (CurrentChar)
                {
                    case ',':
                        MoveNext();
                        break;

                    case ';':
                        return description;
                    default:
                        throw new BindingException(
                            string.Format(
                                "Unexpected character {0} at position {1} in {2} - expected string-end, ',' or ';'",
                                CurrentChar,
                                CurrentIndex,
                                FullText));
                }
            }
        }

        protected BindingMode ReadBindingMode()
        {
            return (BindingMode) ReadEnumerationValue(typeof (BindingMode));
        }

        protected string ReadTextUntilNonQuotedOccurrenceOfAnyOf(params char[] terminationCharacters)
        {
            Dictionary<char, bool> terminationLookup = terminationCharacters.ToDictionary(c => c, c => true);
            SkipWhiteSpace();
            var toReturn = new StringBuilder();

            while (!IsComplete)
            {
                char currentChar = CurrentChar;
                if (currentChar == '\'' || currentChar == '\"')
                {
                    string subText = ReadQuotedString();
                    toReturn.Append(currentChar);
                    toReturn.Append(subText);
                    toReturn.Append(currentChar);
                    continue;
                }

                if (terminationLookup.ContainsKey(currentChar))
                {
                    break;
                }

                toReturn.Append(currentChar);
                MoveNext();
            }

            return toReturn.ToString();
        }

        protected string ReadTargetPropertyName()
        {
            return ReadValidCSharpName();
        }

        protected void SkipWhiteSpaceAndOptionSeparators()
        {
            SkipWhiteSpaceAndCharacters(',');
        }

        protected void SkipWhiteSpaceAndDescriptionSeparators()
        {
            SkipWhiteSpaceAndCharacters(';');
        }
    }
}